// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package syscall

import (
	"runtime"
	"unsafe"
)

type SysProcAttr struct {
	Chroot     string      // Chroot。
	Credential *Credential // 凭证。
	Ptrace     bool        // 启用跟踪。
	Setsid     bool        // 创建会话。
	// Setpgid将子进程的进程组ID设置为Pgid，
	// 或者，如果Pgid==0，则设置为新子进程的进程ID。
	Setpgid bool
	// SETCTY将子进程的控制终端设置为
	// 文件描述符Ctty。Ctty必须是子进程中的描述符编号
	// ：procatr的索引。文件夹。
	// 只有当Setsid为true时，这才有意义。
	Setctty bool
	Noctty  bool // 从控制终端分离fd 0 
	Ctty    int  // 控制TTY fd 
	// 前台将子进程组放置在前台。
	// 这意味着Setpgid。Ctty字段必须设置为
	// 控制TTY的描述符。
	// 与Setctty不同，在本例中，Ctty必须是父进程中的描述符
	// 编号。
	Foreground bool
	Pgid       int    // 如果设置了PGID，则为子进程组ID。
	Pdeathsig  Signal // 表示当其父进程死亡时（仅限Linux和FreeBSD）进程将得到的信号
}

const (
	_P_PID = 0

	_PROC_PDEATHSIG_CTL = 11
)

// 在运行时包中实现。
func runtime_BeforeFork()
func runtime_AfterFork()
func runtime_AfterForkInChild()

// 分叉，将fd复制到0上。。len（fd）和exec（argv0、argvv、envv）在child中。
// 如果dup或exec失败，请将errno错误写入管道。
// /（管道在exec上关闭，因此如果exec成功，它将关闭。）
// 在子函数中，此函数不能获取任何锁，因为
// 它们可能在分叉时已被锁定。这意味着
// 没有重新调度，没有malloc调用，也没有新的堆栈段。
// 出于同样的原因，编译器不会对其进行测试。
// 对RawSyscall的调用没有问题，因为它们是不增加堆栈的程序集
// 函数。
// go:norace 
func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) {
	// 如果任何
	// 声明需要堆分配（例如err1），请在顶部声明所有变量。
	var (
		r1     uintptr
		err1   Errno
		nextfd int
		i      int
	)

	// 记录父母的PID，以便孩子可以测试自己是否死亡。
	ppid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)

	// 注意下面的fds洗牌的副作用。
	// 确保nextfd超出任何当前打开的文件，这样我们就不会冒覆盖任何文件的风险。即将呼叫fork。
	fd := make([]int, len(attr.Files))
	nextfd = len(attr.Files)
	for i, ufd := range attr.Files {
		if nextfd < int(ufd) {
			nextfd = int(ufd)
		}
		fd[i] = int(ufd)
	}
	nextfd++

	// 不再分配或调用非汇编函数。
	runtime_BeforeFork()
	r1, _, err1 = RawSyscall(SYS_FORK, 0, 0, 0)
	if err1 != 0 {
		runtime_AfterFork()
		return 0, err1
	}

	if r1 != 0 {
		// 父母；return PID 
		runtime_AfterFork()
		return int(r1), 0
	}

	// Fork成功，现在在child中。

	// 如果请求，请启用跟踪。
	if sys.Ptrace {
		_, _, err1 = RawSyscall(SYS_PTRACE, uintptr(PTRACE_TRACEME), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 会话ID 
	if sys.Setsid {
		_, _, err1 = RawSyscall(SYS_SETSID, 0, 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 设置进程组
	if sys.Setpgid || sys.Foreground {
		// 将子进程放入进程组。
		_, _, err1 = RawSyscall(SYS_SETPGID, 0, uintptr(sys.Pgid), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	if sys.Foreground {
		// 这真的应该是pid_t，但是_C_int（又名int32）是
		// 通常是等效的。
		pgrp := _C_int(sys.Pgid)
		if pgrp == 0 {
			r1, _, err1 = RawSyscall(SYS_GETPID, 0, 0, 0)
			if err1 != 0 {
				goto childerror
			}

			pgrp = _C_int(r1)
		}

		// 将流程组放在前台。
		_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSPGRP), uintptr(unsafe.Pointer(&pgrp)))
		if err1 != 0 {
			goto childerror
		}
	}

	// 恢复信号掩码。我们在TIOCSPGRP之后这样做是为了避免
	// 让内核向进程组发送SIGTTOU信号。
	runtime_AfterForkInChild()

	// Chroot 
	if chroot != nil {
		_, _, err1 = RawSyscall(SYS_CHROOT, uintptr(unsafe.Pointer(chroot)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 用户和组
	if cred := sys.Credential; cred != nil {
		ngroups := uintptr(len(cred.Groups))
		groups := uintptr(0)
		if ngroups > 0 {
			groups = uintptr(unsafe.Pointer(&cred.Groups[0]))
		}
		if !cred.NoSetGroups {
			_, _, err1 = RawSyscall(SYS_SETGROUPS, ngroups, groups, 0)
			if err1 != 0 {
				goto childerror
			}
		}
		_, _, err1 = RawSyscall(SYS_SETGID, uintptr(cred.Gid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
		_, _, err1 = RawSyscall(SYS_SETUID, uintptr(cred.Uid), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// Chdir 
	if dir != nil {
		_, _, err1 = RawSyscall(SYS_CHDIR, uintptr(unsafe.Pointer(dir)), 0, 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 父级死亡信号
	if sys.Pdeathsig != 0 {
		switch runtime.GOARCH {
		case "386", "arm":
			_, _, err1 = RawSyscall6(SYS_PROCCTL, _P_PID, 0, 0, _PROC_PDEATHSIG_CTL, uintptr(unsafe.Pointer(&sys.Pdeathsig)), 0)
		default:
			_, _, err1 = RawSyscall6(SYS_PROCCTL, _P_PID, 0, _PROC_PDEATHSIG_CTL, uintptr(unsafe.Pointer(&sys.Pdeathsig)), 0, 0)
		}
		if err1 != 0 {
			goto childerror
		}

		// 如果父级已经死亡，则发出自我信号。在极少数情况下，这可能会导致
		// 重复信号，但当
		// 使用SIGKILL时，这并不重要。
		r1, _, _ = RawSyscall(SYS_GETPPID, 0, 0, 0)
		if r1 != ppid {
			pid, _, _ := RawSyscall(SYS_GETPID, 0, 0, 0)
			_, _, err1 := RawSyscall(SYS_KILL, pid, uintptr(sys.Pdeathsig), 0)
			if err1 != 0 {
				goto childerror
			}
		}
	}

	// Pass 1：寻找fd[i]<i，并将其移到len（fd）上方
	// 这样Pass 2就不会踩到它以后需要的fd上。
	if pipe < nextfd {
		_, _, err1 = RawSyscall(SYS_FCNTL, uintptr(pipe), F_DUP2FD_CLOEXEC, uintptr(nextfd))
		if err1 != 0 {
			goto childerror
		}
		pipe = nextfd
		nextfd++
	}
	for i = 0; i < len(fd); i++ {
		if fd[i] >= 0 && fd[i] < int(i) {
			if nextfd == pipe { // 不要踩在管道上
				nextfd++
			}
			_, _, err1 = RawSyscall(SYS_FCNTL, uintptr(fd[i]), F_DUP2FD_CLOEXEC, uintptr(nextfd))
			if err1 != 0 {
				goto childerror
			}
			fd[i] = nextfd
			nextfd++
		}
	}

	// 将2:dup fd[i]传递到i上。
	for i = 0; i < len(fd); i++ {
		if fd[i] == -1 {
			RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
			continue
		}
		if fd[i] == int(i) {
			// dup2（i，i）在Linux上不会清除close on exec标志，
			// 在其他地方可能也不会。
			_, _, err1 = RawSyscall(SYS_FCNTL, uintptr(fd[i]), F_SETFD, 0)
			if err1 != 0 {
				goto childerror
			}
			continue
		}
		// 新的fd不是在exec上创建的，
		// 这正是我们想要的。
		_, _, err1 = RawSyscall(SYS_DUP2, uintptr(fd[i]), uintptr(i), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 按照惯例，我们不会关闭我们从
	// 开始的fd，因此如果len（fd）<3，根据需要关闭0,1,2。
	// 知道自己继承了fds>=3的程序需要
	// 才能在exec上关闭它们。
	for i = len(fd); i < 3; i++ {
		RawSyscall(SYS_CLOSE, uintptr(i), 0, 0)
	}

	// 从tty中分离fd 0 
	if sys.Noctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, 0, uintptr(TIOCNOTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 将控制tty设置为Ctty 
	if sys.Setctty {
		_, _, err1 = RawSyscall(SYS_IOCTL, uintptr(sys.Ctty), uintptr(TIOCSCTTY), 0)
		if err1 != 0 {
			goto childerror
		}
	}

	// 执行时间。
	_, _, err1 = RawSyscall(SYS_EXECVE,
		uintptr(unsafe.Pointer(argv0)),
		uintptr(unsafe.Pointer(&argv[0])),
		uintptr(unsafe.Pointer(&envv[0])))

childerror:
	// 在管道上发送错误代码
	RawSyscall(SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err1)), unsafe.Sizeof(err1))
	for {
		RawSyscall(SYS_EXIT, 253, 0, 0)
	}
}
